
from django.shortcuts import render
from redis.commands.search.reducers import count

from utils.pagination import Pagination
from web import models
from web.forms.issues import IssuesModelForm, IssuesReplyModelForm,InviteModelForm
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from django.urls import reverse
import json
from django.utils.safestring import mark_safe
from utils.encrypt import uid
import datetime

# 下拉列表的的类，用于前端生成url使用
class SelectFilter(object):
    def __init__(self,name,data_list,request):
        self.data_list = data_list # 把调用函数时，传递过来的数据进行初始化操作
        self.request = request
        self.name = name
    def __iter__(self):
        yield mark_safe('<select class="select2" multiple="multiple" style="width:100%;">')
        for item in self.data_list:
            key = str(item[0])
            text = item[1]
            selected = ''
            # 获取url上面的所有的参数
            value_list = self.request.GET.getlist(self.name)
            if key in value_list:
                selected = 'selected'
                value_list.remove(key)
            else:
                value_list.append(key)
            query_dict = self.request.GET.copy() #  <QueryDict: {'status': ['1', '7'], 'priority': ['danger']}>
            query_dict._mutable = True # 开启之后才可以修改url中的携带的变量
            query_dict.setlist(self.name,value_list) # 直接在原对象上面修改，所以不需要赋值。修改什么变量就输入要修改的变量和值。其余的不会修改
            # 跟分页组件的结合，当点击筛选条件时无论分页在多少页都会从全部数据中筛选，不能在当前分页中的数据中筛选。所以一旦点击筛选将会把分页的参数page=xx 从url中移除
            if 'page' in query_dict:
                query_dict.pop('page')
            # 利用request.path_info来组合成url
            # 去除url中没有参数的时候只有一个问号的问题
            param_url = query_dict.urlencode()# query_dict.urlencode()把字典中的数据转换成?变量=xx&变量=xx格式
            if param_url:
                url = '{}?{}'.format(self.request.path_info,param_url)
            else:
                url = self.request.path_info
            html = '<option value="{url}" {selected}>{text}</option>'.format(url=url,selected=selected,text=text)
            yield mark_safe(html)
        yield mark_safe('</select>')


# 写一个选择框的可迭代的对象，前端循环生成url使用
class CheckFilter(object):
    def __init__(self,name,data_list,request):
        self.data_list = data_list # 把调用函数时，传递过来的数据进行初始化操作
        self.request = request
        self.name = name
    def __iter__(self):
        # yield mark_safe('<a><input type="checkbox" /> 新建  </a>')
        # yield mark_safe('<a><input type="checkbox" /> 处理中  </a>')
        # yield mark_safe('<a><input type="checkbox" /> 已解决  </a>')
        ## 下面自动生成的a标签，在每次刷新或者是点击的时候就要重新生成一遍。即a标签中的url也会自动生成一遍，所以需要在value_list(在生成a标签时
        #   会重新生成下url携带的参数和值)，所以在url参数中如果有数据时要在value_list中把对应的移除掉，这样重新生成时不会重复
        # 每次点击a标签跳转的地址就会根据现有的url中携带的参数，再重新把所有的a标签的href都再生成一遍。比如原来携带的参数：?status=2，那么所有重新生成的a标签
        # 将会在这个参数的基础上再增加自己的标签，例如status=7的a标签：?status=2&status=7，priority=success的a标签href将生成：?status=2&priority=success
        for item in self.data_list:
            key = str(item[0])
            text = item[1]
            ck = ''
            # 通过request获取当前url中携带的status参数。获取url中的参数用.getlist(要获取的变量)
            value_list = self.request.GET.getlist(self.name)
            if key in value_list:
                # 如果用户请求的url中status和当前循环的key相等，则把这个标签添加属性：checked 即为选中状态
                # 因为是每次点击a标签跳转到新的href地址时，将会把a标签中的href都再重新生成一遍，所以原来url携带了参数的要在value_list中移除，
                # 这样通过value_list生成的url才不会导致参数重复
                ck = 'checked'
                value_list.remove(key)
            else:
                value_list.append(key)
            # 当前的url后面的参数进行复制，数据形式是字典，如果一个参数有多个数据时，多个数据放在列表中
            query_dict = self.request.GET.copy() #  <QueryDict: {'status': ['1', '7'], 'priority': ['danger']}>
            query_dict._mutable = True # 开启之后才可以修改url中的携带的变量
            query_dict.setlist(self.name,value_list) # 直接在原对象上面修改，所以不需要赋值。修改什么变量就输入要修改的变量和值。其余的不会修改
            # 跟分页组件的结合，当点击筛选条件时无论分页在多少页都会从全部数据中筛选，不能在当前分页中的数据中筛选。所以一旦点击筛选将会把分页的参数page=xx 从url中移除
            if 'page' in query_dict:
                query_dict.pop('page')
            # 利用request.path_info来组合成url
            # 去除url中没有参数的时候只有一个问号的问题
            param_url = query_dict.urlencode()# query_dict.urlencode()把字典中的数据转换成?变量=xx&变量=xx格式
            if param_url:
                url = '{}?{}'.format(self.request.path_info,param_url)
            else:
                url = self.request.path_info
            tpl = '<a class="cell" href="{url}"><input type="checkbox" {ck} /><label>{text}</label></a>'
            html = mark_safe(tpl.format(url=url,ck=ck,text=text))
            yield html

@csrf_exempt
def issues(request,project_id):
    if request.method == 'GET':
        # 左面的筛选页面
        # 设置允许筛选的的页面。通过url中? 后面携带的参数来进行筛选
        allow_filter_name = ['issues_type','status','priority','assign','attention']
        # 筛选条件根据用户通过get传过来的参数实现。如：？status=1&status=1&priority=1 同一个字段的条件用或，不同的字段用与
        condition = {}
        for name in allow_filter_name:
            value_list = request.GET.getlist(name)
            if not value_list:
                continue
            condition['{}__in'.format(name)] = value_list # field__in：[1,2,3],代表field字段的值在列表[1,2,3]中
        # 对数据进行分页
        queryset = models.Issues.objects.filter(project_id=project_id).filter(**condition)
        page_object = Pagination(request, queryset)
        page_queryset = page_object.page_queryset  ## 获取分完页面的数据，把这个数据传给前端好显示分页的效果
        page_string = page_object.html()  ## 获取生成的页码

        form = IssuesModelForm(request)
        project_issues_type =  models.IssuesType.objects.filter(project_id=project_id).values_list('id','title')

        project_total_user = [(request.tracer.project.creator_id,request.tracer.project.creator.username)]
        join_user = models.ProjectUser.objects.filter(project_id=project_id).values_list('user_id','user__username') # __是跨表到外键表来查询
        project_total_user.extend(join_user)

        invite_form = InviteModelForm()
        context = {
            'issues_object_list': page_queryset,  ## 分完页后的数据
            'page_string': page_string,  ## 生成的页码
            'form': form,
            'invite_form':invite_form,
            # 'status_filter':CheckFilter('status',models.Issues.status_choices,request), # models.Issues.status_choices获取
            #                                     # 的是元组的数据。这条数据要想被前端循环，必须用迭代器
            # 'priority_filter': CheckFilter('priority',models.Issues.priority_choices, request),
            # 'issues_type_filter': CheckFilter('issues_type',project_issues_type, request),
            # 这个地方可以写成一个字典，在前端用两层循环展示数据，就不用再用一个前端写一个了
            'filter_list':[
                {'title':'问题类型','filter':CheckFilter('issues_type',project_issues_type, request)},
                {'title': '状态', 'filter': CheckFilter('status',models.Issues.status_choices,request),},
                {'title': '优先级', 'filter': CheckFilter('priority',models.Issues.priority_choices, request)},
                {'title': '指派者', 'filter': SelectFilter('assign', project_total_user, request)},
                {'title': '关注者', 'filter': SelectFilter('attention', project_total_user, request)},
            ]
        }
        return render(request,'manage/issues.html',context)
    form = IssuesModelForm(request,data=request.POST)
    if form.is_valid():
        form.instance.project = request.tracer.project
        form.instance.creator = request.tracer.user
        form.save()
        return render(request, 'manage/issues.html')
    return JsonResponse({'status':False,'error':form.errors})

def issues_detail(request,project_id,issues_id):
    """ 编辑问题 """
    issues_object = models.Issues.objects.filter(id=issues_id,project_id=project_id).first()
    form = IssuesModelForm(request,instance=issues_object) # 让当前的数据实例等于查询出来的数据。即页面将默认显示查询出来数据的内容

    return render(request,'manage/issues_detail.html',{'form':form,'issues_object':issues_object})

@csrf_exempt
def issues_record(request,project_id,issues_id):
    """ 初始化操作记录 """
    """
        1. 外键表的操作:例如表IssuesReply中的问题外键issues，建立表时设置的字段是issues并定义为表Issues的外键，要进行跨表查询的话
            1 先在表IssuesReply中找出需要跨表的数据：reply_list
            2 通过查找出来的数据，用 数据.外键 就可以跨到外键表中了，例如：reply_list[0].issues 就进入到表Issues中了。跨表后就可以用 .字段
                这个格式来处理跨表后的表中的数据
            3 如果跨表后需要再进入跨表的外键表中，就再次用：数据.外键.外键 就可以进入跨表的外键表中
        2. 定义表的外键，例如是通过默认的id来连接，在数据库中实际存储的是：外键_id，例如：issues_id 是通过外键issues 连接表Issues的id字段
            所以在查询时可以用issues_id=issues_id，其中前面的issues_id代表的是表中的字段，后面的issues_id代表的是传入的参数
            issues__project=request.tracer.project：前面issues__project代表的是
            表IssuesReply中的外键issues关联的表Issues，Issues表中外键project所关联的项目表Project 所代表的项目对象
        3. 注意跨表查询和在数据库中查询表示的语法不同。操作数据库中的数据用__来跨表，其余的用.外键来进行跨表，可以一次跨好几个表
            issues__project__creator_id=22 意思是：
                表IssuesReply通过外键issues跨到表Issues中，表Issues再通过外键__project跨到表Project中，表Project再通过外键__creator
                跨到表UserInfo中，再通过_id来获取表UserInfo中的字段id的数值
            issues__project=request.tracer.project是通过跨表查询获取到项目对象，再跟request中储存的项目对象进行比较
    """
    if request.method == 'GET':
        reply_list = models.IssuesReply.objects.filter(issues_id=issues_id,issues__project=request.tracer.project)
        # print(111111111,reply_list[0].issues.project.id) # 通过外键来进行跨表
        # 将queryset 转换为json。即将数据构造成一个个的字典并放在列表中
        data_list = []
        for row in reply_list:
            data = {
                'id':row.id,
                'reply_type_text':row.get_reply_type_display(),
                'content':row.content,
                'creator':row.creator.username,
                'datetime':row.create_datetime.strftime('%Y-%m-%d %H:%M:%S'),
                'parent_id':row.reply_id,
            }
            data_list.append(data)
        return JsonResponse({'status':True,'data':data_list})
    form = IssuesReplyModelForm(data=request.POST)
    if form.is_valid():
        form.instance.issues_id = issues_id
        form.instance.reply_type = 2
        form.instance.creator = request.tracer.user
        instance = form.save()
        info = {
            'id': instance.id,
            'reply_type_text': instance.get_reply_type_display(),
            'content': instance.content,
            'creator': instance.creator.username,
            'datetime': instance.create_datetime.strftime('%Y-%m-%d %H:%M:%S'),
            'parent_id': instance.reply_id,
        }
        return JsonResponse({'status':True,'data':info})
    return JsonResponse({'status':False,'error':form.errors})

@csrf_exempt
def issues_change(request,project_id,issues_id):
    # 根据传过来的issues_id，在数据库中找到要操作的对象
    issues_object = models.Issues.objects.filter(id=issues_id,project_id=project_id).first()
    # 接收前端传送过来的json格式的字典数据
    post_dict = json.loads(request.body.decode('utf-8'))
    name = post_dict.get('name')
    value = post_dict.get('value')
    # 根据name的值，获取数据库中的字段对象，来判断这个字段是否允许为空
    field_object = models.Issues._meta.get_field(name)

    def create_reply_record(content):
        new_object = models.IssuesReply.objects.create(
            reply_type=1,
            issues=issues_object,
            content=change_record,
            creator=request.tracer.user,
        )
        # 通过上面新增的对象，把对象中的数据转换为一个字典
        new_reply_dict = {
            'id': new_object.id,
            'reply_type_text': new_object.get_reply_type_display(),
            'content': new_object.content,
            'creator': new_object.creator.username,
            'datetime': new_object.create_datetime.strftime('%Y-%m-%d %H:%M:%S'),
            'parent_id': new_object.reply_id,
        }
        return new_reply_dict

    # 数据接收下来后，分成三种来更新
    # 1 文本数据，即在输入框中自己输入的数据
    if name in ['subject','desc','start_date','end_date']:
        # 判断用户输入的数据是否为空
        if not value:
            # 再判断数据库中的字段是否为空
            if not field_object.null:
                return JsonResponse({'status':False,'error':'数据不能为空！'})
            # 通过判断这个字段在数据库中允许为空时，在用户为输入数据时就设为：None
            setattr(issues_object, name, None)
            issues_object.save()
            # 在前端显示更新后的数据，格式为：字段更新为空
            change_record = '{}更新为空'.format(field_object.verbose_name)
        else:
            # 利用反射更新对象中的属性和对应的值。
            setattr(issues_object,name,value)
            issues_object.save()
            # 在前端显示更新后的数据，格式为：字段更新为数值
            change_record = '{}更新为{}'.format(field_object.verbose_name,value)
        # return JsonResponse({'status': True, 'data': new_reply_dict})
        return JsonResponse({'status':True,'data':create_reply_record(change_record)})
    # 2 ForeignKey数据的更新
    if name in ['issues_type','module','parent','assign']:
        # 用户选择为空时
        if not value:
            # 数据库不允许为空时
            if not field_object.null:
                return JsonResponse({'status':False,'error':'数据不能为空！'})
            # 通过判断这个字段在数据库中允许为空时，在用户为输入数据时就设为：None
            setattr(issues_object, name, None)
            issues_object.save()
            # 在前端显示更新后的数据，格式为：字段更新为空
            change_record = '{}更新为空'.format(field_object.verbose_name)
        else:
            if name == 'assign': # 如果字段名是指派
                # 需要判断是否是项目的创建者
                if value == str(request.tracer.project.creator_id):
                    instance = request.tracer.project.creator
                # 是否是项目的参与者
                else:
                    project_user_object = models.ProjectUser.objects.filter(project_id=project_id,user_id=value).first()
                    if project_user_object:
                        instance = project_user_object.user
                    else:
                        instance = None
                # 如果既不是创建者又不是参与者，那么instance=None
                if not instance:
                    return JsonResponse({'status':False,'error':'你选择的值不存在'})
                issues_object.save()
                change_record = '{}更新为{}'.format(field_object.verbose_name, str(instance))
            else:
                # 条件判断，用户输入的值是自己的值。field_object.rel.model是跳转到field_object关联的外键表中，将直接操作关联表中的数据
                instance = field_object.remote_field.model.objects.filter(id=value,project_id=project_id).first()
                if not instance:
                    return JsonResponse({'status': False, 'error': '数据不能为空！'})
                # 利用反射更新对象中的属性和对应的值。
                setattr(issues_object, name, instance)
                issues_object.save()
                # 在前端显示更新后的数据，格式为：字段更新为数值
                change_record = '{}更新为{}'.format(field_object.verbose_name, str(instance))

        return JsonResponse({'status': True, 'data': create_reply_record(change_record)})
    # 3 choices字段
    if name in ['priority','status','mode']:
        select_text = None
        # 字段通过.choices直接就可以拿到字段的choices元组
        for key,text in field_object.choices:
            if str(key) == value:
                select_text = text
        if not select_text:
            return JsonResponse({'status':False,'error':'你选择的值不存在'})
        setattr(issues_object, name, value)
        issues_object.save()
        change_record = '{}更新为{}'.format(field_object.verbose_name, select_text)
        return JsonResponse({'status': True, 'data': create_reply_record(change_record)})
    # 4 M2M字段
    if name == 'attention':
        # 关注者attention，是多对多的关系，所以值value是一个列表：{'name':'attention','value':[1,2,3,4,5,6]}
        # 所以判断value的数据格式，是否是一个list
        if not isinstance(value,list):
            return JsonResponse({'status':False,'error':'数据格式错误'})
        if not value:
            issues_object.attention.set(value)
            issues_object.save()
            change_record = '{}更新为空'.format(field_object.verbose_name)
            return JsonResponse({'status': True, 'data': create_reply_record(change_record)})
        else:
            user_dict = {request.tracer.project.creator_id:request.tracer.project.creator.username} # 获取项目的创建者
            # 获取当前项目的所有成员
            project_user_list = models.ProjectUser.objects.filter(project_id=project_id)
            for item in project_user_list:
                user_dict[item.user_id] = item.user.username # 通过循环把项目的参与者也添加到定义的字典中。格式为： 参与者id：参与者名字
            username_list = []
            # 循环前端传过来的value，并根据传过来的数据判断是否在字典user_dict中，user_dict中储存的是项目的创建者和所有的参与者
            for user_id in value:
                username = user_dict.get(int(user_id)) # 在字典中获取这个数据，如果存在的话，说明是参与者或者是创建者。通过字典的键获取字典的值
                if not username:
                    return JsonResponse({'status': False, 'error': '数据错误'})
                username_list.append(username)
            issues_object.attention.set(value)
            issues_object.save()
            change_record = '{}更新为{}'.format(field_object.verbose_name,",".join(username_list))
            return JsonResponse({'status': True, 'data': create_reply_record(change_record)})
    return JsonResponse({'status':False,'error':'用户不存在'})

# 生成邀请码
def invite_url(request,project_id):
    """ 生成邀请码 """

    form = InviteModelForm(data=request.POST)
    if form.is_valid():
        """ 表单验证通过时：
            1 创建随机的邀请码
            2 验证码保存到数据库
            3 限制：只有创建者才能邀请
        """
        if request.tracer.user != request.tracer.project.creator:
            form.add_error('period','无权创建邀请码')
            return JsonResponse({'status':False,'error':form.errors})
        # 根据encrypt.py中编写的随机字符串函数，生成一个随机的邀请码。并把生成的随机码和相关的数据放在表ProjectInvite中
        random_invite_code = uid(request.tracer.user.mobile_phone)
        form.instance.project = request.tracer.project
        form.instance.code = random_invite_code
        form.instance.creator = request.tracer.user
        form.save()
        # 把获取的邀请码返回给前端，并在前端的页面上展示出来
        # reverse 是反向生成url，根据参数：invite_join 代表的url来反向生成。invite_join在url.py中通过name='invite_join'来查找，
        # 并把第二个参数的字典数据的值放在反向生成的url的最后面。如：issues/invite/join/xxxxxxxxxx，所以还需要在最前端拼接上域名和端口号，才能形成完整的url
        url_path = reverse('invite_join',kwargs={'code':random_invite_code})
        # 完整的url拼接。scheme代表的是最前面是http或者是https，这个数据可以在request.scheme中直接获取。host获取的是主机IP或者是域名 和端口号
        url = '{scheme}://{host}{path}'.format(
            scheme = request.scheme,
            host = request.get_host(),
            path = url_path,
        )
        return JsonResponse({'status':True,'data':url})

    return JsonResponse({'status':False,'error':form.errors})

def invite_join(request,code):
    """ 访问生成的邀请码函数 """
    current_datetime = datetime.datetime.now()
    invite_object = models.ProjectInvite.objects.filter(code=code).first()
    if not invite_object:
        return render(request,'web/invite_join.html',{'error':'邀请码不存在'})
    if invite_object.project.creator == request.tracer.user:
        return render(request,'web/invite_join.html',{'error':'创建者无需再加入项目'})
    # 如果项目参与者表中项目对象跟项目邀请码表中项目对象相等，并且参与者等于当前的登录用户。也就是登录的用户已经是项目的成员
    exists = models.ProjectUser.objects.filter(project=invite_object.project,user=request.tracer.user).exists()
    if exists:
        return render(request, 'web/invite_join.html', {'error': '已加入项目无需再加入'})
    # 根据价格策略查询最多允许的人数
    ## 最多允许的成员
    # max_member = request.tracer.price_policy.project_member
    # 1. 首先根据项目邀请码中的项目创建者，去交易记录表中查找这个用户的最近交易记录。根据最新的交易记录的额度来判断用户所绑定的价格策略，从而判断此用户的最大成员权限
    max_transaction = models.Transaction.objects.filter(user=invite_object.project.creator).order_by('-id').first()
    # 2. 判断这个用户正在使用的价格策略，从而确定这个用户的邀请成员的最大权限
    if max_transaction.price_policy.category == 1:
        max_member = max_transaction.price_policy.project_member # 如果是免费额度，就通过跨表来查询免费额度允许的项目成员数
    else:
        if max_transaction.end_datetime < current_datetime: # 如果价格策略中的最后时间小于当前的时间，就证明已经过期了。过期就使用免费额度了
            free_object = models.PricePolicy.objects.filter(category=1).first() # 从价格策略中获取免费额度的对象
            max_member = free_object.project_member # 获取免费额度的最大项目成员数
        else:
            max_member = max_transaction.price_policy.project_member

    ## 目前项目的所有成员。如果加入创建者的话，需要再把这个数据+1，因为这样查询出来的数据不包括创建者
    current_member = models.ProjectUser.objects.filter(project=invite_object.project).count()
    if current_member+1 >= max_member:
        return render(request, 'web/invite_join.html', {'error': '项目成员已达最大数量'})

    # 判断邀请码是否已过期。先获取当前时间，有效期=创建时间+有效期的时间

    limit_datetime = invite_object.create_datetime + datetime.timedelta(minutes=invite_object.period)
    if current_datetime > limit_datetime:
        return render(request, 'web/invite_join.html', {'error': '邀请码已过期'})
    # 根据表中的count字段来判断是否有数量限制
    if invite_object.count:
        # 表中已使用的数量跟限制数量的比较
        if invite_object.use_count >= invite_object.count:
            return render(request, 'web/invite_join.html', {'error': '邀请码数量已使用完'})
        invite_object.use_count += 1
        invite_object.save()
    # 无数量限制的话就直接保存到项目参与者表中
    models.ProjectUser.objects.create(user=request.tracer.user,project=invite_object.project)
    invite_object.project.join_count += 1
    invite_object.project.save()
    return render(request, 'web/invite_join.html',{'project':invite_object.project})




































